﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Common.Utility
{
   /// <summary>
   /// Provides a thread-safe dictionary for use with data binding.
   /// </summary>
   /// <typeparam name="TKey">Specifies the type of the keys in this collection.</typeparam>
   /// <typeparam name="TValue">Specifies the type of the values in this collection.</typeparam>
   [DebuggerDisplay("Count={Count}")]
   public class ObservableDictionary<TKey, TValue> : IDictionary<TKey, TValue>, INotifyCollectionChanged, INotifyPropertyChanged
   {
      private const string CountString = "Count";
      private const string IndexerName = "Item[]";
      private const string KeysName = "Keys";
      private const string ValuesName = "Values";

      private IDictionary<TKey, TValue> _Dictionary;
      protected IDictionary<TKey, TValue> Dictionary
      {
         get
         {
            return _Dictionary;
         }
      }

      #region Constructors
      public ObservableDictionary()
      {
         _Dictionary = new Dictionary<TKey, TValue>();
      }
      public ObservableDictionary(IDictionary<TKey, TValue> dictionary)
      {
         _Dictionary = new Dictionary<TKey, TValue>(dictionary);
      }
      public ObservableDictionary(IEqualityComparer<TKey> comparer)
      {
         _Dictionary = new Dictionary<TKey, TValue>(comparer);
      }
      public ObservableDictionary(int capacity)
      {
         _Dictionary = new Dictionary<TKey, TValue>(capacity);
      }
      public ObservableDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
      {
         _Dictionary = new Dictionary<TKey, TValue>(dictionary, comparer);
      }
      public ObservableDictionary(int capacity, IEqualityComparer<TKey> comparer)
      {
         _Dictionary = new Dictionary<TKey, TValue>(capacity, comparer);
      }
      #endregion

      #region IDictionary<TKey,TValue> Members

      public void Add(TKey key, TValue value)
      {
         Insert(key, value, true);
      }

      public bool ContainsKey(TKey key)
      {
         return Dictionary.ContainsKey(key);
      }

      public ICollection<TKey> Keys
      {
         get
         {
            return Dictionary.Keys;
         }
      }

      public bool Remove(TKey key)
      {
         if (key == null)
            throw new ArgumentNullException("key");

         TValue value;
         Dictionary.TryGetValue(key, out value);
         var removed = Dictionary.Remove(key);
         if (removed)
            //OnCollectionChanged(NotifyCollectionChangedAction.Remove, new KeyValuePair<TKey, TValue>(key, value));
            OnCollectionChanged();
         return removed;
      }

      public bool TryGetValue(TKey key, out TValue value)
      {
         return Dictionary.TryGetValue(key, out value);
      }

      public ICollection<TValue> Values
      {
         get
         {
            return Dictionary.Values;
         }
      }

      public TValue this[TKey key]
      {
         get
         {
            TValue value;
            return TryGetValue(key, out value) ? value : default(TValue);
         }
         set
         {
            Insert(key, value, false);
         }
      }

      #endregion

      #region ICollection<KeyValuePair<TKey,TValue>> Members

      public void Add(KeyValuePair<TKey, TValue> item)
      {
         Insert(item.Key, item.Value, true);
      }

      public void Clear()
      {
         if (Dictionary.Count > 0)
         {
            Dictionary.Clear();
            OnCollectionChanged();
         }
      }

      public bool Contains(KeyValuePair<TKey, TValue> item)
      {
         return Dictionary.Contains(item);
      }

      public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex)
      {
         Dictionary.CopyTo(array, arrayIndex);
      }

      public int Count
      {
         get
         {
            return Dictionary.Count;
         }
      }

      public bool IsReadOnly
      {
         get
         {
            return Dictionary.IsReadOnly;
         }
      }

      public bool Remove(KeyValuePair<TKey, TValue> item)
      {
         return Remove(item.Key);
      }


      #endregion

      #region IEnumerable<KeyValuePair<TKey,TValue>> Members

      public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator()
      {
         return Dictionary.GetEnumerator();
      }

      #endregion

      #region IEnumerable Members

      IEnumerator IEnumerable.GetEnumerator()
      {
         return ((IEnumerable)Dictionary).GetEnumerator();
      }

      #endregion

      #region INotifyCollectionChanged Members

      public event NotifyCollectionChangedEventHandler CollectionChanged;

      #endregion

      #region INotifyPropertyChanged Members

      public event PropertyChangedEventHandler PropertyChanged;

      #endregion

      public void AddRange(IDictionary<TKey, TValue> items)
      {
         if (items == null)
            throw new ArgumentNullException("items");

         if (items.Count > 0)
         {
            if (Dictionary.Count > 0)
            {
               if (items.Keys.Any((k) => Dictionary.ContainsKey(k)))
                  throw new ArgumentException("An item with the same key has already been added.");
               else
                  foreach (var item in items)
                     Dictionary.Add(item);
            }
            else
               _Dictionary = new Dictionary<TKey, TValue>(items);

            OnCollectionChanged(NotifyCollectionChangedAction.Add, items.ToArray());
         }
      }

      private void Insert(TKey key, TValue value, bool add)
      {
         if (key == null)
            throw new ArgumentNullException("key");

         TValue item;
         if (Dictionary.TryGetValue(key, out item))
         {
            if (add)
               throw new ArgumentException("An item with the same key has already been added.");
            if (Equals(item, value))
               return;
            Dictionary[key] = value;

            OnCollectionChanged(NotifyCollectionChangedAction.Replace, new KeyValuePair<TKey, TValue>(key, value), new KeyValuePair<TKey, TValue>(key, item));
            OnPropertyChanged(key.ToString());
         }
         else
         {
            Dictionary[key] = value;

            OnCollectionChanged(NotifyCollectionChangedAction.Add, new KeyValuePair<TKey, TValue>(key, value));
            OnPropertyChanged(key.ToString());
         }
      }

      private void OnPropertyChanged()
      {
         OnPropertyChanged(CountString);
         OnPropertyChanged(IndexerName);
         OnPropertyChanged(KeysName);
         OnPropertyChanged(ValuesName);
      }

      protected virtual void OnPropertyChanged(string propertyName)
      {
         if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
      }

      private void OnCollectionChanged()
      {
         OnPropertyChanged();
         if (CollectionChanged != null)
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
      }

      private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> changedItem)
      {
         OnPropertyChanged();
         if (CollectionChanged != null)
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, changedItem, 0));
      }

      private void OnCollectionChanged(NotifyCollectionChangedAction action, KeyValuePair<TKey, TValue> newItem, KeyValuePair<TKey, TValue> oldItem)
      {
         OnPropertyChanged();
         if (CollectionChanged != null)
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItem, oldItem, 0));
      }

      private void OnCollectionChanged(NotifyCollectionChangedAction action, IList newItems)
      {
         OnPropertyChanged();
         if (CollectionChanged != null)
            CollectionChanged(this, new NotifyCollectionChangedEventArgs(action, newItems, 0));
      }
   }
}
